/*******************************************************************************
 * @file Copyright 2009 The CREATE Lab at CMU
 * 
 *       This file is part of JubJub, an open source project.
 *       http://code.google.com/p/jubjub/
 * 
 *       Redistribution and use in source and binary forms, with or without
 *       modification, are permitted provided that the following conditions are
 *       met:
 * 
 *       Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 * 
 *       Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 * 
 *       Neither the name of Carnegie Mellon nor the names of its contributors
 *       may be used to endorse or promote products derived from this software
 *       without specific prior written permission.
 * 
 *       THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *       "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *       LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 *       PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 *       HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 *       SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 *       LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *       DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 *       THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *       (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 *       OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *******************************************************************************/

package edu.cmu.ri.createlab.jj.shapes;

import static edu.cmu.ri.createlab.jj.shapes.ShapeDefs.varHeight;
import static edu.cmu.ri.createlab.jj.shapes.ShapeDefs.varValIndent;
import static edu.cmu.ri.createlab.jj.shapes.ShapeDefs.varWidth;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.image.ImageObserver;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;

import javax.swing.JComponent;
import javax.swing.JPanel;

import edu.cmu.ri.createlab.jj.blocks.*;
import edu.cmu.ri.createlab.jj.blocks.BlockDefs.BlockTypes;
import edu.cmu.ri.createlab.jj.blocks.BlockDefs.ExpressTypes;
import edu.cmu.ri.createlab.jj.blocks.BlockDefs.VarTypes;
import edu.cmu.ri.createlab.jj.shapes.JJShapeFactory.PathTypes;
import edu.cmu.ri.createlab.jj.ui.JJCTextField;
import edu.cmu.ri.createlab.jj.ui.JJPanelInterface;
import edu.cmu.ri.createlab.jj.ui.JJTextField;
import edu.cmu.ri.createlab.jj.visitors.BVDrawer;
import static edu.cmu.ri.createlab.jj.shapes.ShapeDefs.*;

public class RefShape implements JJShapeInterface
{
   ///top and bot edge refer to the main horizontal for a shape, where
   ///a line is drawn to show block placement and where the next block
   ///will be aligned to 'snap' together.
   int topEdge;
   int botEdge;
   int x;
   int y;
   double scale;
   PathTypes pt;
   int size;
   BlockPath path;
   RefBlock block;
   JJPanelInterface parent;
   ArrayList<RefShape> params;
   ArrayList<RefShape> returns;
   boolean selected;
   JJTextField field;
   boolean fieldDisplayed;
   
   public RefShape(int x, int y, RefBlock ref, double scale, JJPanelInterface parent)
   {
      size = 0;
      params = new ArrayList<RefShape>();
      returns = new ArrayList<RefShape>();
      this.parent = parent;
      fieldDisplayed = false;
      if(ref.getBlock() == null) pt = PathTypes.VAR_BLOCK;
      else switch(ref.getBlock().getType())
      {
      case BLOCK:
      case CODE:
         pt = PathTypes.NORMAL_BLOCK;
         size = ref.getParams().size() + ref.getReturns().size();
         break;
      case OBJECT:
         pt = PathTypes.NORMAL_BLOCK;
         if(ref.getParams().isEmpty()) size = 0;
         else size = ref.getParams().get(0).getParams().size() 
            + ref.getParams().get(0).getReturns().size();
         break;
      case VAR:
         pt = PathTypes.VAR_BLOCK;
         if(((VarBlock)ref.getBlock()).isLiteral())
         {
            field = new JJTextField();
            field.setBlock((VarBlock)ref.getBlock());
            field.setPreferredSize(new Dimension((int)(varWidth*scale), (int)(varHeight*scale)));
            //((JComponent) parent).add(field);
            field.setBounds(x, y, (int)(varWidth*scale), (int)(varHeight*scale));
         }
         break;
      case EXPRESSION:
         if(((ExpressionBlock)ref.getBlock()).getEType() == ExpressTypes.ASSIGN)
            pt = PathTypes.ASSIGN_BLOCK;
         else pt = PathTypes.OP_BLOCK;
         break;
      case COMMENT:
         pt = PathTypes.COMMENT_BLOCK;
         break;
      case FLOW:
         if(ref.getIndex() == 0) pt = PathTypes.FLOW_TOP_BLOCK;
         else if(ref.getIndex() == 3) pt = PathTypes.FLOW_BOT_BLOCK;
         else pt = PathTypes.FLOW_MID_BLOCK;
         break;
      default:
         break;
      }
      path = new JJShapeFactory().buildPath(x, y, pt, size, scale);
      topEdge = path.getTop();
      botEdge = path.getBot();
      this.x = x;
      this.y = y;
      this.scale = scale;
      block = ref;
      selected = false;
      if(ref.getBlock() == null) return;
      if(ref.getBlock().getType() == BlockTypes.OBJECT)
      {
         if(!ref.getParams().isEmpty())
         {
            recurseRef(ref.getParams().get(0), pt, parent);
         }
      }
      else recurseRef(ref, pt, parent);
   }
   
   public void recurseRef(RefBlock ref,PathTypes pt, JJPanelInterface parent)
   {
      int tempX, tempY;
      switch(pt)
      {
      case NORMAL_BLOCK:
         tempX = x + (int)(varIndent*scale);
         tempY = y + (int)(topHeight*scale);
         Iterator<RefBlock> it = ref.getParams().iterator();
         while(it.hasNext()) 
         {
            params.add(new RefShape(tempX, tempY, it.next(), scale, parent));
            tempY += (int)(middleHeight*scale);
         }
         it = ref.getReturns().iterator();
         while(it.hasNext())
         {
            returns.add(new RefShape(tempX, tempY, it.next(), scale, parent));
            tempY += (int)(middleHeight*scale);
         }
         break;
      case ASSIGN_BLOCK:
         if(ref.getParams().isEmpty()) return;
         tempX = x + (int)(nameXOffset*scale);
         tempY = y + (int)(varSpacing*scale);
         params.add(new RefShape(tempX, tempY, ref.getParams().get(0),scale, parent));
         tempX = x + (int)(assignAttachX*scale);
         tempY = y;
         params.add(new RefShape(tempX, tempY, ref.getParams().get(1), scale, parent));
         break;
      case OP_BLOCK:
         if(ref.getParams().isEmpty()) return;
         tempX = x + (int)(opVarOffset1*scale);
         tempY = y + (int)(varSpacing*scale);
         params.add(new RefShape(tempX, tempY, ref.getParams().get(0),scale, parent));
         tempX = x + (int)(opVarOffset2*scale);
         params.add(new RefShape(tempX, tempY, ref.getParams().get(1),scale, parent));
         break;
      case COMMENT_BLOCK:
         //TODO create text field
         JJCTextField tempField = new JJCTextField();
         tempX = x+ (int)(cTextIndent*scale);
         tempY = y+ (int)(topHeight*scale);
         tempField.setBlock((CommentBlock)ref.getBlock());
         ((JComponent)parent).add(tempField);
         tempField.setBounds(tempX, tempY, (int)(cTextWidth*scale), (int)(cTextHeight*scale));
         break;
      case FLOW_TOP_BLOCK:
      case FLOW_MID_BLOCK:
      case FLOW_BOT_BLOCK:
         if(!ref.getParams().isEmpty())
         {
            tempX = x + (int)(varIndent*scale);
            tempY = y + (int)(varSpacing*scale);
            params.add(new RefShape(tempX, tempY, ref.getParams().get(0),scale, parent));
         }
         break;
      default:
         break;
      }
   }
   
   public RefBlock getRef(int x, int y)
   {
      RefBlock ref = null;
      ref = this.block;
      return ref;
   }
   
   public RefBlock getRef()
   {
      return this.block;
   }
   
   public BlockPath getPath()
   {
      return path;
   }
   
   public void clear()
   {
      topEdge = 0;
      botEdge = 0;
      x = 0;
      y = 0;
      scale = 0;
      path = null;
      block = null;
      params.clear();
      returns.clear();
   }
   
   public boolean addBlocks(LinkedList<RefBlock> blocks, int x, int y)
   {
      for(int i = 0; i < params.size(); i++)
      {
         if(params.get(i).contains(x, y))
         {
            Iterator<RefBlock> it = blocks.iterator();
            while(it.hasNext())
            {
               RefBlock tempBlock = it.next();
               if(tempBlock.getBlock() != null && 
                     (tempBlock.getBlock().getType() == BlockTypes.VAR || 
                           (tempBlock.getBlock().getType() == BlockTypes.EXPRESSION && 
                                 ((ExpressionBlock)tempBlock.getBlock()).getEType() != ExpressTypes.ASSIGN)))
               {
                  if(!params.get(i).addBlocks(blocks, x, y))
                  {
                     RefBlock changeBlock;
                     if(block.getBlock() != null && block.getBlock().getType() == BlockTypes.OBJECT)
                        changeBlock = block.getParams().get(0);
                     else changeBlock = block;
                     changeBlock.remParam(changeBlock.getParams().get(i));
                     changeBlock.addParam(tempBlock, i);
                     RefShape tempShape = params.get(i);
                     params.remove(i);
                     params.add(i, new RefShape(tempShape.x, tempShape.y, tempBlock, scale, this.parent));
                  }
                  return true;
               }
            }
         }
      }
      for(int i = 0; i < returns.size(); i++)
      {
         if(returns.get(i).contains(x, y))
         {
            Iterator<RefBlock> it = blocks.iterator();
            while(it.hasNext())
            {
               RefBlock tempBlock = it.next();
               if(tempBlock.getBlock() != null && 
                     (tempBlock.getBlock().getType() == BlockTypes.VAR || 
                           (tempBlock.getBlock().getType() == BlockTypes.EXPRESSION && 
                                 ((ExpressionBlock)tempBlock.getBlock()).getEType() != ExpressTypes.ASSIGN)))
               {
                  RefBlock updateBlock = block;
                  if(block.getBlock() != null && block.getBlock().getType() == BlockTypes.OBJECT)
                  {
                     updateBlock = block.getParams().get(0);
                  }
                  if(!returns.get(i).addBlocks(blocks, x, y))
                  {
                     updateBlock.remReturn(updateBlock.getReturns().get(i));
                     updateBlock.addReturn(tempBlock, i);
                     RefShape tempShape = returns.get(i);
                     returns.remove(i);
                     returns.add(i, new RefShape(tempShape.x, tempShape.y, tempBlock, scale, this.parent));
                  }
                  return true;
               }
            }
         }
      }
      return false;
   }
   
   
   public boolean contains(int x, int y)
   {
      return path.contains(x, y);
   }
   
   public boolean contains(int y)
   {
      return y >= topEdge && y <= botEdge;
   }
   
   
   public boolean clicked()
   {
      //TODO check what was clicked and respond accordingly
      return false;
   }
   
   public boolean clicked(int x, int y)
   {
      if(block.getBlock() != null && block.getBlock().getType() == BlockTypes.VAR
            && ((VarBlock)block.getBlock()).isLiteral())
      {
         ((JComponent)parent).add(field);
         fieldDisplayed = true;
         return true;
      }
      for(int i = 0; i < params.size(); i++)
      {
         if(params.get(i).contains(x, y))
            return params.get(i).clicked(x, y);
      }
      for(int i = 0; i < returns.size(); i++)
      {
         if(returns.get(i).contains(x, y))
            return returns.get(i).clicked(x, y);
      }
      return false;
   }
   

   
   public void draw(Graphics g, ImageObserver o, BVDrawer drawer)
   {
      // TODO Auto-generated method stub
      drawer.setScale(scale);
      drawer.setStart(x, y);
      drawer.setGraphics(g);
      drawer.setObserver(o);
      block.accept(drawer);
//      g.setColor(Color.cyan);
//      ((Graphics2D)g).draw(this.path.getPath());
//      g.setColor(Color.black);
      Iterator<RefShape> it = params.iterator();
      while(it.hasNext())
      {
         RefShape tempRef = it.next();
         tempRef.draw(g, o, drawer);
//         g.setColor(Color.blue);
//         ((Graphics2D)g).draw(tempRef.path.getPath());
//         g.setColor(Color.black);
      }
      it = returns.iterator();
      while(it.hasNext())
         it.next().draw(g, o, drawer);
   }

   
   public int[] getBounds()
   {
      // TODO Auto-generated method stub
      Rectangle tempR = path.getPath().getBounds();
      int[] bounds = new int[4];
      bounds[0] = (int) tempR.getMinX();
      bounds[1] = (int) tempR.getMinY();
      bounds[2] = (int) tempR.getMaxX();
      bounds[3] = (int) tempR.getMaxY();
      return bounds;
   }

   public void drawYellow(int x, int y, Graphics g)
   {
      for(int i = 0; i < params.size(); i++)
      {
         if(params.get(i).contains(x, y))
         {
            params.get(i).drawYellow(x, y, g);
            return;
         }
      }
      for(int i = 0; i < returns.size(); i++)
      {
         if(returns.get(i).contains(x, y))
         {
            returns.get(i).drawYellow(x, y, g);
            return;
         }
      }
      if(block.getBlock() != null 
            && (block.getBlock().getType() == BlockTypes.VAR 
                  || (block.getBlock().getType() == BlockTypes.EXPRESSION &&
                  ((ExpressionBlock)block.getBlock()).getEType() != ExpressTypes.ASSIGN))
         )
      {
         g.setColor(Color.black);
         ((Graphics2D)g).draw(path.getPath());
         g.setColor(Color.black);
      }
   }
   
   public void setOrigin(int x, int y)
   {
      //a new path is created to prevent cumulative rounding errors for repeated repositioning.
      this.x = x;
      this.y = y;
      path = (new JJShapeFactory().buildPath(x, y, pt, size, scale));
      topEdge = path.getTop();
      botEdge = path.getBot();

   }
   
   
   public void setScale(double scale)
   {
      //a new path is created to prevent cumulative rounding errors for repeated repositioning.
      this.scale = scale;
      path = (new JJShapeFactory().buildPath(x, y, pt, size, scale));
      topEdge = path.getTop();
      botEdge = path.getBot();
   }
   
   public int getTop()
   {
      return topEdge;
   }
   
   public int getBot()
   {
      return botEdge;
   }
   
   public boolean isNearTop(int y)
   {
      return Math.abs(y-this.getTop()) < Math.abs(y-this.getBot());
   }

}
